package com.hk.commons.util;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.json.JsonReadFeature;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.*;
import com.fasterxml.jackson.databind.Module;
import com.fasterxml.jackson.databind.module.SimpleModule;
import com.fasterxml.jackson.databind.ser.FilterProvider;
import com.fasterxml.jackson.databind.ser.PropertyFilter;
import com.fasterxml.jackson.databind.ser.impl.SimpleBeanPropertyFilter;
import com.fasterxml.jackson.databind.ser.impl.SimpleFilterProvider;
import com.fasterxml.jackson.databind.util.StdDateFormat;
import com.fasterxml.jackson.datatype.hibernate5.jakarta.Hibernate5JakartaModule;
import com.fasterxml.jackson.datatype.jdk8.Jdk8Module;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateDeserializer;
import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateTimeDeserializer;
import com.fasterxml.jackson.datatype.jsr310.deser.LocalTimeDeserializer;
import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateSerializer;
import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateTimeSerializer;
import com.fasterxml.jackson.datatype.jsr310.ser.LocalTimeSerializer;
import com.hk.commons.JsonResult;
import com.hk.commons.jackson.BigIntegerToStringSerializer;
import com.hk.commons.jackson.EnhanceAnnotationIntrospector;
import com.hk.commons.jackson.LongToStringSerializer;
import com.hk.commons.query.page.QueryPage;
import com.hk.commons.query.page.SimpleQueryPage;
import com.hk.commons.util.date.DateTimeUtils;
import lombok.AccessLevel;
import lombok.NoArgsConstructor;
import lombok.SneakyThrows;

import java.io.File;
import java.io.IOException;
import java.io.OutputStream;
import java.math.BigInteger;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.format.DateTimeFormatter;
import java.util.*;

/**
 * JSON Utils
 *
 * @author kevin
 * @date 2018-07-17 14:48
 */
@NoArgsConstructor(access = AccessLevel.PRIVATE)
public final class JsonUtils {

    public static final String IGNORE_ENTITY_SERIALIZE_FIELD_FILTER_ID = "fieldFilter";

    public static final String DYNAMIC_SERIALIZE_FIELD_FILTER_ID = "dynamicFieldFilter";

    private static final Module[] JAVA8_MODULE;

    private static final SimpleModule SIMPLE_MODULE;

    private static final boolean HIBERNATE_MODULE_ENABLED = org.springframework.util.ClassUtils.isPresent("org.hibernate.Session", null)
            && org.springframework.util.ClassUtils.isPresent("com.fasterxml.jackson.datatype.hibernate5.Hibernate5Module", null);
    /*
     * 为什么要使用 volatile 修饰，因为 volatile 可以禁止指令重排，
     * 在多线程下，a线程创建对象到一半时，b线程可能会读取到a线程创建对象的中间态，而这个创建的对象并没有真正创建完成。
     * 强烈建议使用单例模式时，要使用此关键了修饰
     * <p>
     * 用 volatile 修饰会被 sonar kube扫出来是个bug ?
     */
    private static ObjectMapper mapper;

    private static ObjectMapper indentMapper;

    static {
        var moduleList = new ArrayList<Module>();

        var JAVA_TIME_MODULE = new JavaTimeModule();
        JAVA_TIME_MODULE.addSerializer(LocalDateTime.class,
                new LocalDateTimeSerializer(DateTimeFormatter.ofPattern(DateTimeUtils.YYYY_MM_DD_HH_MM_SS)));

        JAVA_TIME_MODULE.addDeserializer(LocalDateTime.class, new LocalDateTimeDeserializer(
                DateTimeFormatter.ofPattern(DateTimeUtils.YYYY_MM_DD_HH_MM_SS)));

        JAVA_TIME_MODULE.addSerializer(LocalDate.class,
                new LocalDateSerializer(DateTimeFormatter.ofPattern(DateTimeUtils.YYYY_MM_DD)));
        JAVA_TIME_MODULE.addDeserializer(LocalDate.class,
                new LocalDateDeserializer(DateTimeFormatter.ofPattern(DateTimeUtils.YYYY_MM_DD)));

        JAVA_TIME_MODULE.addSerializer(LocalTime.class,
                new LocalTimeSerializer(DateTimeFormatter.ofPattern(DateTimeUtils.HH_MM_SS)));
        JAVA_TIME_MODULE.addDeserializer(LocalTime.class,
                new LocalTimeDeserializer(DateTimeFormatter.ofPattern(DateTimeUtils.HH_MM_SS)));
        moduleList.add(JAVA_TIME_MODULE);
        moduleList.add(new Jdk8Module());
        /*
         *   这里使用 toArray 方法转换为数组时，toArray(new Module[0]) 与 toArray(new Module[moduleList.size()])的区别:
         *   转换集合为数组的时候，有两种方式：使用初始化大小的数组（这里指的是初始化大小的时候使用了集合的size()方法）和空数组。
         *   在低版本的 Java 中推荐使用初始化大小的数组，因为使用反射调用去创建一个合适大小的数组相对较慢。
         *   但是在 openJDK 6 之后的高版本中方法被优化了，传入空数组相比传入初始化大小的数组，效果是相同的甚至有时候是更优的。
         *   因为使用 concurrent 或 synchronized 集合时，如果集合进行了收缩，toArray()和size()方法可能会发生数据竞争，此时传入初始化大小的数组是危险的。
         * @see https://stackoverflow.com/questions/174093/toarraynew-myclass0-or-toarraynew-myclassmylist-size
         */
        JAVA8_MODULE = moduleList.toArray(new Module[0]);

        SimpleModule module = new SimpleModule();
        module.addSerializer(Long.class, LongToStringSerializer.getInstance());
//        module.addSerializer(String.class, new XssStringJsonSerializer());
        module.addSerializer(BigInteger.class, BigIntegerToStringSerializer.getInstance());
//        module.addDeserializer(Set.class, new Jackson2ArrayOrStringDeserializer());
        SIMPLE_MODULE = module;

    }

    public static Module[] java8Modules() {
        return JAVA8_MODULE;
    }

    public static Module hibernate5Modules() {
        return Hibernate5Modules.hibernate5Module;
    }

    /**
     * 所有module
     *
     * @param containSimpleModule 是否包含 simpleModule ，simpleModule中有 Long 类型的 LongToStringSerializer，在接口返回前端的时间需要
     *                            传true，不然long类型会丢失精度，
     *                            在 redis缓存的时候需要传false ,不然在保存redis的数据就会为string类型，查询的时候会强制类型转换失败
     */
    public static List<Module> getModuleList(boolean containSimpleModule) {
        List<Module> modules = ArrayUtils.asArrayList(JAVA8_MODULE);
        if (containSimpleModule) {
            modules.add(SIMPLE_MODULE);
        }
        if (HIBERNATE_MODULE_ENABLED) {
            modules.add(Hibernate5Modules.hibernate5Module);
        }
        return modules;
    }

    private static ObjectMapper getMapper() {
        if (Objects.isNull(mapper)) {
            synchronized (JsonUtils.class) {
                if (Objects.isNull(mapper)) {
                    var m = new ObjectMapper();
                    configure(m, getModuleList(true), AuditField.AUDIT_FIELD_ARRAY.toArray(new String[0]));
                    mapper = m;
                }
            }
        }
        return mapper;
    }

    private static class CustomFilterProvider extends SimpleFilterProvider {

        /**
         * 父类的这个方法，如果找不到，会报错。
         * 主要是防止同一个类上声明了 @JsonFilter(value = DYNAMIC_SERIALIZE_FIELD_FILTER_ID) 时，
         * 调用了 {@link  #serializeIgnoreProperties(Object, String...)}
         * 也调用 {@link  #serialize(Object)} 找不到 PropertyFilter 报错的问题。
         * <p>
         * 示例:
         * <pre>
         *     public static void main(String[] args) {
         *         Demo bean = new Demo().setId("12").setName("abd");
         *         System.out.println(JsonUtils.serializeIgnoreProperties(bean, "id"));
         *         System.out.println(JsonUtils.serialize(bean));
         *     }
         *
         *     &#064;Data
         *     &#064;JsonFilter(value  = DYNAMIC_SERIALIZE_FIELD_FILTER_ID)
         *     &#064;Accessors(chain = true)
         *     public static class Demo {
         *
         *         private String id;
         *
         *         private String name;
         *     }
         *     </pre>
         */
        @Override
        public PropertyFilter findPropertyFilter(Object filterId, Object valueToFilter) {
            return Objects.isNull(filterId) ? null : _filtersById.get(filterId.toString());
        }
    }

    public static void configure(ObjectMapper om, List<Module> modules, String... exceptFields) {
        var exceptSet = ArrayUtils.asHashSet(exceptFields);
        var filterProvider = new CustomFilterProvider();
        filterProvider.addFilter(IGNORE_ENTITY_SERIALIZE_FIELD_FILTER_ID,
                SimpleBeanPropertyFilter.serializeAllExcept(exceptSet));
        om.setAnnotationIntrospector(new EnhanceAnnotationIntrospector());
        om.setDateFormat(StdDateFormat.instance)
                .setTimeZone(TimeZone.getTimeZone(Constants.TIME_ZONE_SHANGHAI))
                .setLocale(Locale.CHINA)
                .enable(JsonReadFeature.ALLOW_JAVA_COMMENTS.mappedFeature())//允许 /* */或者//这种类型的注释出现
                .disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES)// 设置输入时忽略在JSON字符串中存在但Java对象实际没有的属性
                .disable(SerializationFeature.FAIL_ON_EMPTY_BEANS)
                .configure(SerializationFeature.WRITE_DATE_KEYS_AS_TIMESTAMPS, false)
//              .configure(JsonParser.Feature.ALLOW_UNQUOTED_CONTROL_CHARS, true) // 允许特殊字符转义，默认为false
//                .configure(JsonParser.Feature.ALLOW_SINGLE_QUOTES, true) // 允许单引号，默认为false
//                .enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL)// 如果配置为了会输出类名
                .registerModules(modules.toArray(new Module[0])) // 注册java 8 日期 module
                .setFilterProvider(filterProvider);
//                .getSerializerProvider().setNullValueSerializer(NullEmptyJsonSerializer.INSTANCE);// 空值处理为空串
    }

    private static ObjectMapper getIndentMapper() {
        if (Objects.isNull(indentMapper)) {
            synchronized (JsonUtils.class) {
                if (Objects.isNull(indentMapper)) {
                    var iMapper = new ObjectMapper();
                    iMapper.enable(SerializationFeature.INDENT_OUTPUT);
                    configure(iMapper, getModuleList(true), AuditField.AUDIT_FIELD_ARRAY.toArray(new String[0]));
                    indentMapper = iMapper;
                }
            }
        }
        return indentMapper;
    }

    /**
     * 将对象序列化为JSON string.
     *
     * @param obj obj
     * @return json str
     */
    public static String serialize(Object obj) {
        return serialize(obj, false);
    }

    /**
     * 将对象序列化为 JSON string
     *
     * @param obj    obj
     * @param indent indent
     * @return json str
     */
    @SneakyThrows(value = {JsonProcessingException.class})
    public static String serialize(Object obj, boolean indent) {
        if (Objects.isNull(obj)) {
            return null;
        }
        return indent ? getIndentMapper().writeValueAsString(obj)
                : getMapper().writeValueAsString(obj);
    }

    @SneakyThrows(value = {JsonProcessingException.class})
    public static String serialize(Object obj, FilterProvider filterProvider, boolean indent) {
        if (Objects.isNull(obj)) {
            return null;
        }
        return indent ? getIndentMapper().writer(filterProvider).writeValueAsString(obj)
                : getMapper().writer(filterProvider).writeValueAsString(obj);
    }

    /**
     * 序列化忽略指定字段，需要在实体类上，加 @JsonFilter("myFilter") {@link com.fasterxml.jackson.annotation.JsonFilter(DYNAMIC_SERIALIZE_FIELD_FILTER_ID)}
     * 对Map,或没有标注 JsonFilter 的对象中的属性不能忽略
     *
     * @param obj              序列化的对象
     * @param ignoreProperties 要忽略的属性
     */
    public static String serializeIgnoreProperties(Object obj, String... ignoreProperties) {
        if (Objects.isNull(obj)) {
            return null;
        }
        if (ArrayUtils.isEmpty(ignoreProperties)) {
            return serialize(obj, false);
        }
        SimpleBeanPropertyFilter propertyFilter = SimpleBeanPropertyFilter.serializeAllExcept(ignoreProperties);
        return serialize(obj, new SimpleFilterProvider().addFilter(DYNAMIC_SERIALIZE_FIELD_FILTER_ID, propertyFilter), false);
    }

    /**
     * 序列化为 字符串，忽略指定属性，
     * <p>
     * 个人觉得这个方法写的不太好，先转换为Map,再用map序列化，
     * 更多好的写法可以参考 {@link #serializeIgnoreProperties(Object, String...)}
     *
     * @param obj               obj
     * @param containsNullValue 是否 包含属性值为null 属性
     * @param ignoreProperties  要忽略的属性名
     * @return json str
     */
    public static String serializeIgnoreProperties(Object obj, boolean containsNullValue, String... ignoreProperties) {
        if (obj instanceof Map<?, ?> map) {
            if (!containsNullValue || ArrayUtils.isNotEmpty(ignoreProperties)) {
                Map<Object, Object> filterMap = new HashMap<>();
                for (Map.Entry<?, ?> entry : map.entrySet()) {
                    Object key = entry.getKey();
                    Object value = entry.getValue();
                    if (!containsNullValue && Objects.isNull(value)) {
                        continue;
                    }
                    if (ArrayUtils.contains(ignoreProperties, key)) {
                        continue;
                    }
                    filterMap.put(key, value);
                }
                return serialize(filterMap, false);
            }
            return serialize(map, false);
        }
        return serialize(BeanUtils.beanToMap(obj, containsNullValue, ignoreProperties), false);
    }

    /**
     * 序列化到 byte 数组
     *
     * @param obj obj
     * @return byte[]
     */
    @SneakyThrows(value = {JsonProcessingException.class})
    public static byte[] serializeToByte(Object obj, boolean indent) {
        if (Objects.isNull(obj)) {
            return null;
        }
        return indent ? getIndentMapper().writeValueAsBytes(obj) : getMapper().writeValueAsBytes(obj);
    }


    /**
     * 序列化到 outputStream
     *
     * @param obj          obj
     * @param outputStream outputStream
     */
    @SneakyThrows(value = {IOException.class})
    public static void serializeToOutputStream(Object obj, OutputStream outputStream) {
        if (Objects.nonNull(obj)) {
            getMapper().writeValue(outputStream, obj);
        }
    }

    /**
     * 序列化为文件
     *
     * @param obj  obj
     * @param file file
     */
    @SneakyThrows(value = {IOException.class})
    public static void serializeToFile(Object obj, File file) {
        if (Objects.nonNull(obj)) {
            getMapper().writeValue(file, obj);
        }
    }

    /**
     * 将json 字符串反序列化为对象
     *
     * @param json  json str
     * @param clazz Class
     * @param <T>   T
     * @return T
     */
    @SneakyThrows(value = {IOException.class})
    public static <T> T deserialize(String json, Class<T> clazz) {
        if (StringUtils.isEmpty(json)) {
            return null;
        }
        return getMapper().readValue(json, clazz);
    }

    @SneakyThrows(value = {IOException.class})
    public static <T> T deserialize(String json, JavaType javaType) {
        if (StringUtils.isEmpty(json)) {
            return null;
        }
        return getMapper().readValue(json, javaType);
    }

    @SneakyThrows(value = {IOException.class})
    public static <T> T deserialize(byte[] json, Class<T> clazz) {
        if (ArrayUtils.isEmpty(json)) {
            return null;
        }
        return getMapper().readValue(json, clazz);
    }

    /**
     * 将json 字符串反序列化为对象集合
     *
     * @param <T>   T
     * @param json  json str
     * @param clazz class
     * @return 序列化的List
     */
    public static <T> List<T> deserializeList(String json, Class<T> clazz) {
        return deserialize(json, ArrayList.class, clazz);
    }

    public static <T> List<T> deserializeList(byte[] json, Class<T> clazz) {
        return deserialize(json, List.class, clazz);
    }

    public static <T> JsonResult<T> deserializeJsonResult(String json, Class<T> clazz) {
        return deserialize(json, JsonResult.class, clazz);
    }

    public static <T> JsonResult<List<T>> deserializeJsonResultList(String json, Class<T> clazz) {
        return deserialize(json, JsonResult.class, ArrayList.class, clazz);
    }

    public static <T> JsonResult<Set<T>> deserializeJsonResultSet(String json, Class<T> clazz) {
        return deserialize(json, JsonResult.class, HashSet.class, clazz);
    }

    public static <T> JsonResult<QueryPage<T>> deserializeJsonResultQueryPage(String json, Class<T> clazz) {
        return deserialize(json, JsonResult.class, SimpleQueryPage.class, clazz);
    }

    /**
     * 将json 字符串反序列化为对象集合
     *
     * @param <T>   T
     * @param json  json str
     * @param clazz class
     * @return 序列化的List
     */
    public static <T> Set<T> deserializeSet(String json, Class<T> clazz) {
        return deserialize(json, HashSet.class, clazz);
    }

    @SneakyThrows(value = {IOException.class})
    public static <T> T deserialize(byte[] json, Class<?> rawType, Class<?> parametrized) {
        if (ArrayUtils.isEmpty(json)) {
            return null;
        }
        var mapper = getMapper();
        return mapper.readValue(json, mapper.getTypeFactory().constructParametricType(rawType, parametrized));
    }

    @SneakyThrows(value = {IOException.class})
    public static <K, V> Map<K, V> deserializeMap(String json,
                                                  Class<K> keyClass, Class<V> valueClass) {
        if (StringUtils.isEmpty(json)) {
            return null;
        }
        var mapper = getMapper();
        var mapType = mapper.getTypeFactory()
                .constructMapType(HashMap.class, keyClass, valueClass);
        return mapper.readValue(json, mapType);
    }

    @SneakyThrows(value = {IOException.class})
    public static <T> T deserialize(String json, TypeReference<T> type) {
        if (StringUtils.isEmpty(json)) {
            return null;
        }
        var mapper = getMapper();
        return mapper.readValue(json, type);
    }

    /**
     * 转换为 List 类型的 Map
     *
     * @param json       json
     * @param keyClass   keyClass
     * @param valueClass value
     */
    @SneakyThrows(value = {IOException.class})
    public static <K, V> List<Map<K, V>> deserializeListMap(String json,
                                                            Class<K> keyClass, Class<V> valueClass) {
        if (StringUtils.isEmpty(json)) {
            return null;
        }
        var mapper = getMapper();
        var mapType = mapper.getTypeFactory()
                .constructMapType(HashMap.class, keyClass, valueClass);
        return mapper.readValue(json, mapper.getTypeFactory().constructParametricType(List.class, mapType));
    }

    /**
     * 反序列化json 字符串到对象 多级泛型: 如：JsonResult<List<SysUser>>
     *
     * @return 序列化的对象
     */
    @SneakyThrows(value = {IOException.class})
    public static <T> T deserialize(String json, Class<T> rawType,
                                    Class<?>... parameterClasses) {
        if (StringUtils.isEmpty(json)) {
            return null;
        }
        var mapper = getMapper();
        if (ArrayUtils.isNotEmpty(parameterClasses)) {
            if (parameterClasses.length == 1) {
                return mapper.readValue(json, mapper.getTypeFactory()
                        .constructParametricType(rawType, parameterClasses[0]));
            }
            int len = parameterClasses.length;
            var parametrized1 = parameterClasses[len - 2];
            var parametrized2 = parameterClasses[len - 1];
            var type = mapper.getTypeFactory()
                    .constructParametricType(parametrized1, parametrized2);
            for (var i = len - 3; i >= 0; i--) {
                var parametrized = parameterClasses[i];
                type = mapper.getTypeFactory().constructParametricType(parametrized, type);
            }
            type = mapper.getTypeFactory().constructParametricType(rawType, type);
            return mapper.readValue(json, type);
        }
        return mapper.readValue(json, rawType);
    }

    /*
        Hibernate5JakartaModule
        添加 hibernate 使用 getOne 查询 懒加载报错的问题
        @see https://stackoverflow.com/questions/24994440/no-serializer-found-for-class-org-hibernate-proxy-pojo
        -javassist-javassist
    */
    private static class Hibernate5Modules {

        static Module hibernate5Module;

        static {
            if (HIBERNATE_MODULE_ENABLED) {
                var Module = new Hibernate5JakartaModule();
                Module.enable(Hibernate5JakartaModule.Feature.FORCE_LAZY_LOADING);
                Module.disable(Hibernate5JakartaModule.Feature.USE_TRANSIENT_ANNOTATION);
                hibernate5Module = Module;
            } else {
                hibernate5Module = null;
            }
        }
    }

}
